<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
  </head>

  <body>
    <style>
      ul {
        list-style: none;
      }
    </style>
    <ul></ul>

    <script>
      /**
       * 如果只是监视第一层的话也只能应用在这道题上了，咱加大点难度，我们整个可深度监听的函数
       * 既然是要给原数据设置监听说明我们必须在传值时保证都是引用而非浅拷贝或者深拷贝，否则最后添加监视的属性也不是我们最开始的
       * 1.首先是递归遍历应该怎么写，终止条件又应该是什么，这里我们可以思考一下，进入下一层递归需要哪些条件？父对象的引用以及当前遍历的子属性键值对吧
       * 也就是说只要父对象对应的子属性还是对象则它还有层级，也就是需要遍历
       * 2.终止条件自然是else即可，也就是说当前已经到最后一层了
       * 3.Object.defineProperty的使用需要注意点细节，稍有不慎容易爆栈，这里说一下为什么会爆栈呢，因为我们是以引用传过来的值，如果在get属性里面访问了obj[key]则会再次触发get，无限循环
       * 有小友问了，那你最后传入时深拷贝一下呗，传入的不是原值引用绑定的也就不是原值引用了，怎么解决呢，难不难受，难受死了，那怎么办呢，
       * 小友回忆一下Object.defineProperty在什么情况下会触发监视，当通过父对象访问子属性时才会触发监视对不对，可以直接传子属性的引用不通过父对象访问
       * 那我们开始直接传obj[key]呗，也就是我的代码里面的value，再寻思一下get使用的value是函数传过来的，岂不是只能用一次，
       * 并不是的，这里利用了JavaScript中函数作用域和作用域链的特性，使得对象的方法能够访问其定义所在的外部作用域中的变量
       * 也就是说value的值会保留下来，相当于我们有了一个存储的标志变量
       * 现在想想set怎么处理呢，给value赋值现在还需要担心爆栈吗，不用啦，咱直接访问地址又没有通过它的父对象，
       * 赋值以后再调用一次渲染函数，手写深度监视拿下拿下😎
       */
      var ul = document.querySelector("ul");
      var person = {
        sex: "男",
        age: "25",
        name: "王大锤",
        height: 28,
        weight: 32,
        kkk: {
          a: 1,
          b: 2,
        },
      };
      function render(element) {
        var str = `<li>姓名：<span>${person.name}</span></li>
                        <li>性别：<span>${person.sex}</span></li>
                        <li>年龄：<span>${person.age}</span></li>
                        <li>身高：<span>${person.height}</span></li>
                        <li>体重：<span>${person.weight}</span></li>
                        <li>test：<span>${person.kkk.a}</span></li>
                        `;
        element.innerHTML = str;
        return true;
      }
      render(ul);
      // 补全代码
      const updateview = (obj) => {
        //递归遍历
        const recursiveCall = (obj, key) => {
          if (typeof obj[key] === "object")
            Object.keys(obj[key]).forEach((item, index) =>
              recursiveCall(obj[key], item)
            );
          else return ListenObject(obj, key, obj[key]);
        };

        //设置对象监听
        const ListenObject = (obj, key, value) => {
          Object.defineProperty(obj, key, {
            get: () => value,
            set: (newValue) => {
                value = newValue;
                render(ul);
            },
          });
        };

        Object.keys(obj).forEach((item, index) => {
          recursiveCall(obj, item);
        });
      };
      updateview(person);
      person.name = "小明";//改变了
      person.kkk.a = 4;//改变了

      //没有改变，因为aaa直接指向a的地址了，而不是通过它的父对象访问的
      let aaa=person.kkk.a;
      aaa.a=5;

      //测试一下value是变成了全局变量还是只是在作用域保留着，防止污染
      // console.log(value);//not defined,说明全局没这个变量
    </script>
  </body>
</html>
